| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- 'use client';
- import { use, useEffect, useRef, useState } from 'react';
- import { useDonationHub } from '@/hooks/useDonationHub';
- import { fetchApi } from '@/lib/utils/client';
- import { GoalProgress } from '@/types/donation';
- import './style.scss';
- type Props = {
- params: Promise<{ widgetToken: string }>;
- searchParams: Promise<{ [key: string]: string|string[]|undefined }>;
- };
- type GoalStyleConfig = {
- id: number;
- title: string;
- style: number;
- startAmount: number;
- targetAmount: number;
- isShowPercent: boolean;
- barColor: string;
- barBackgroundColor: string;
- barHeightPx: number;
- titleFontSizePx: number;
- titleFontColor: string;
- amountFontSizePx: number;
- amountFontColor: string;
- titleFontFamily: string|null;
- amountFontFamily: string|null;
- isActive: boolean;
- };
- const DEFAULT_STYLE: Omit<GoalStyleConfig, 'id'|'title'|'style'|'startAmount'|'targetAmount'|'isActive'> = {
- isShowPercent: true,
- barColor: '#FF6B35',
- barBackgroundColor: '#333333',
- barHeightPx: 30,
- titleFontSizePx: 18,
- titleFontColor: '#FFFFFF',
- amountFontSizePx: 14,
- amountFontColor: '#CCCCCC',
- titleFontFamily: null,
- amountFontFamily: null
- };
- export default function GoalPage({ params, searchParams }: Props) {
- const { widgetToken } = use(params);
- const sp = use(searchParams);
- const configID = typeof sp.configID === 'string' ? parseInt(sp.configID, 10) : null;
- const hubUrl = process.env.NEXT_PUBLIC_API_URL + '/hubs/donation';
- const { goalProgress, setGoalProgress } = useDonationHub(widgetToken, hubUrl);
- const [styleCfg, setStyleCfg] = useState<GoalStyleConfig|null>(null);
- const widgetRef = useRef<HTMLDivElement>(null);
- // 스타일 config 로드
- useEffect(() => {
- const qs = configID ? `?configID=${configID}` : '';
- fetchApi<GoalStyleConfig>(`/api/widget/goal/config/${widgetToken}${qs}`, { silent: true }).then(res => {
- if (res.data) {
- setStyleCfg(res.data);
- }
- }).catch(() => {});
- }, [widgetToken, configID]);
- // 진행률 데이터 로드 (SignalR broadcast 전 초기 표시)
- useEffect(() => {
- const qs = configID ? `?configID=${configID}` : '';
- fetchApi<GoalProgress>(`/api/widget/goal/by-token/${widgetToken}${qs}`, { silent: true }).then(res => {
- if (res.data) {
- setGoalProgress(res.data);
- }
- }).catch(() => {});
- }, [widgetToken, configID, setGoalProgress]);
- // CSS variables 적용 (goalProgress 없어도 styleCfg 기반으로 빈 진행바 스타일링)
- useEffect(() => {
- if (!widgetRef.current) {
- return;
- }
- const el = widgetRef.current;
- const cfg = styleCfg ?? null;
- const titleFontSize = cfg?.titleFontSizePx ?? DEFAULT_STYLE.titleFontSizePx;
- const titleColor = cfg?.titleFontColor ?? DEFAULT_STYLE.titleFontColor;
- const barHeight = cfg?.barHeightPx ?? DEFAULT_STYLE.barHeightPx;
- const barBgColor = cfg?.barBackgroundColor ?? DEFAULT_STYLE.barBackgroundColor;
- const barColor = cfg?.barColor ?? DEFAULT_STYLE.barColor;
- const amountSize = cfg?.amountFontSizePx ?? DEFAULT_STYLE.amountFontSizePx;
- const amountColor = cfg?.amountFontColor ?? DEFAULT_STYLE.amountFontColor;
- const titleFamily = cfg?.titleFontFamily ?? 'inherit';
- const amountFamily = cfg?.amountFontFamily ?? 'inherit';
- const fillPercent = goalProgress ? Math.min(goalProgress.percent, 100) : 0;
- el.style.setProperty('--goal-title-font-size', `${titleFontSize}px`);
- el.style.setProperty('--goal-title-color', titleColor);
- el.style.setProperty('--goal-title-font-family', titleFamily);
- el.style.setProperty('--goal-bar-height', `${barHeight}px`);
- el.style.setProperty('--goal-bar-bg-color', barBgColor);
- el.style.setProperty('--goal-bar-color', barColor);
- el.style.setProperty('--goal-bar-fill-width', `${fillPercent}%`);
- el.style.setProperty('--goal-amount-font-size', `${amountSize}px`);
- el.style.setProperty('--goal-amount-color', amountColor);
- el.style.setProperty('--goal-amount-font-family', amountFamily);
- }, [goalProgress, styleCfg]);
- // goalProgress 없어도 styleCfg 기반으로 빈 진행바 표시 (위젯 즉시 노출)
- const display = goalProgress ?? {
- title: styleCfg?.title ?? '후원 목표',
- startAmount: styleCfg?.startAmount ?? 0,
- targetAmount: styleCfg?.targetAmount ?? 0,
- currentAmount: styleCfg?.startAmount ?? 0,
- percent: 0
- };
- const { title, currentAmount, targetAmount, percent } = display;
- const isShowPercent = styleCfg?.isShowPercent ?? DEFAULT_STYLE.isShowPercent;
- return (
- <div ref={widgetRef} className="goal-widget">
- <div className="goal-title">
- {title}
- </div>
- <div className="goal-bar-wrapper">
- <div className="goal-bar-bg">
- <div className="goal-bar-fill" />
- </div>
- <div className="goal-bar-text">
- {currentAmount.toLocaleString()}원 / {targetAmount.toLocaleString()}원
- {isShowPercent && ` (${percent}%)`}
- </div>
- </div>
- </div>
- );
- }
|